Code Test
Press Shift + Fuck You
Tip 05 : Third-Party Languages Functions Calling
You can call a function from third-party languages like GDScript, C#, Python etc. from a Jenova C++ Script.
Non-Static Function Calling
Let's say we have this function in a built-in GDScript on a Node at Root/Node3D
func add_five_and_double_it(value: int) -> int:
var result = (value + 5) * 2
return resulty
Now to call the function we simply do this in Jenova C++ Script :
// Called On Every Frame
void OnProcess(Variant* _delta)
{
Ref<SomethingShit> msgMan;
@jenova@
@placeholder@
auto node3d = GetNode<Node>("Root/Node3D");
auto result = node3d->call("add_five_and_double_it", 10);
Output("GDScript Function Result : %d", int(result));
}
Simple as that!
Static Function Calling
Now let's say we have a static function in a GDScript :
static func add_nine_and_double_it(value: int) -> int:
var result = (value + 9) * 2
return result
This is how we can call into it :
// Called On Every Frame
void OnProcess(Variant* _delta)
{
auto node = GetNode<Node>("Root/Node3D");
auto script = node->get_script();
if (!script.get_type() == Variant::NIL)
{
auto result = node->call("add_nine_and_double_it", 100);
Output("GDScript Function Result : %d", int(result));
}
}
Note : Previous method also works on static methods but this one is provided as an alternative for static functions.
Calling C++ Script Functions from GDScript
Here's opposite example of what we did above, Let's say we have this C++ function :
Variant SumUp(int a, int b)
{
return a + b;
}
This is how we call it from GDScript:
func _process(delta):
var result = get_node("Root/Obj").call("SumUp", 10, 15);
print("C++ Function Result : %d" % result);
Remember to always return a Variant
to GDScript as all values in GDScript are Variant
.
It's also possible to call another Jenova C++ Script from Jenova C++ Script or GDScript, C# etc.
[ Tip 04 : Using .NET/C# Within Jenova ]
It's possible to integrate .NET and C# directly in Jenova by using external linking just like regular native modules. To do such thing you will need to use DllExport NuGet Package and export functions from your .NET module, Then simply link against it in Jenova.
C# Side
[DllExport] public static bool ManagedOperation(string commandName)
{
return true;
}
C++ Side
extern "C" _declspec(dllimport) bool ManagedOperation(char* commandName)
This method works on all versions of Windows which have .Net Framework installed.
// Godot SDK
#include <Godot/godot.hpp>
#include <Godot/classes/node.hpp>
#include <Godot/classes/input.hpp>
#include <Godot/classes/text_edit.hpp>
#include <Godot/classes/rich_text_label.hpp>
#include <Godot/classes/panel_container.hpp>
#include <Godot/classes/v_box_container.hpp>
#include <Godot/classes/scroll_container.hpp>
#include <Godot/classes/v_scroll_bar.hpp>
#include <Godot/classes/scene_tree.hpp>
#include <Godot/classes/canvas_layer.hpp>
#include <Godot/variant/variant.hpp>
// Shared Header
#include "Code/Include/Shared.hpp"
// Jenova SDK
#include <JenovaSDK.h>
// JenovaLLM SDK
#include <JenovaLLM.h>
// Set Class Name
JENOVA_CLASS_NAME("JenovaLLM Chatbot")
// Namespaces
using namespace godot;
using namespace jenova::sdk;
// Global Memory Identifiers
constexpr const char* llmCoreID = "llmCore";
// Local Variables
bool scrollNeedsUpdate = false;
// Instances
JenovaLLM::Core* llmCore = nullptr;
JenovaLLM::Actor* llmActor = nullptr;
// Helper Templates
template <typename T> T* GetPropertyNode(const String nodePath)
{
T* nodePtr = GetNode<T>("Lab/Stage/Chatbot/" + nodePath);
return nodePtr;
}
// Forward Declarations
void ResponseCallback(JenovaLLM::Actor* actor, const JenovaLLM::Response& response);
// Message Manager Class
class MessageManager : public RefCounted
{
GDCLASS(MessageManager, RefCounted);
protected:
static void _bind_methods()
{
ClassDB::bind_method(D_METHOD("AddNewAIMessage"), &MessageManager::AddNewAIMessage);
ClassDB::bind_method(D_METHOD("AppendNewAIMessage"), &MessageManager::AppendNewAIMessage);
ClassDB::bind_method(D_METHOD("CommitAIMessage"), &MessageManager::CommitAIMessage);
ClassDB::bind_method(D_METHOD("CommitUserMessage"), &MessageManager::CommitUserMessage);
ClassDB::bind_method(D_METHOD("ClearMessages"), &MessageManager::ClearMessages);
ClassDB::bind_method(D_METHOD("GetUserMessage"), &MessageManager::GetUserMessage);
}
public:
void AddNewAIMessage();
void AppendNewAIMessage(String messageContent);
void CommitAIMessage();
String CommitUserMessage();
void ClearMessages();
void GetUserMessage();
PanelContainer* GetLastMessage();
void SetUIState(bool uiState);
};
// Message Manager Instance
Ref<MessageManager> msgMan;
// Message Manager Register/Unregister/Getter
void RegisterMessageManager()
{
// Register Classes
ClassDB::register_class<MessageManager>();
// Finish Reload
sakura::FinishReload("MessageManager");
}
void UnregisterMessageManager()
{
// Prepare for Reload
sakura::PrepareReload("MessageManager");
// Release Class
sakura::Dispose("MessageManager");
}
Ref<MessageManager> GetMessageManager()
{
if (msgMan.is_null() || !msgMan.is_valid()) msgMan.instantiate();
return msgMan;
}
// Start Jenvoa Script
JENOVA_SCRIPT_BEGIN
// Properties
JENOVA_PROPERTY(NodePath, ai_msg_template, "")
JENOVA_PROPERTY(NodePath, user_msg_template, "")
JENOVA_PROPERTY(NodePath, chat_container, "")
JENOVA_PROPERTY(NodePath, chat_scroll, "")
JENOVA_PROPERTY(NodePath, chat_placeholder, "")
JENOVA_PROPERTY(NodePath, control_button_label, "")
// Node Events
void OnAwake(Caller* instance)
{
// Create/Allocate Global LLM Core
llmCore = GlobalPointer<JenovaLLM::Core>(llmCoreID);
if (llmCore == nullptr)
{
llmCore = JenovaLLM::CreateNewCore();
SetGlobalPointer(llmCoreID, llmCore);
}
// Create Global Variables
SetGlobalVariable("IsWaitingForUserInput", false);
SetGlobalVariable("IsChatEnabled", false);
// Initialize Message Manager
msgMan.instantiate();
}
void OnDestroy(Caller* instance)
{
// Release Global LLM Core
if (llmCore)
{
if (!llmCore->Release()) Alert("Error : Failed to Release Jenova LLM Core.");
delete llmCore;
}
// Release Message Manager
msgMan.unref();
}
void OnReady(Caller* instance)
{
// Retrive Global LLM Core
llmCore = GlobalPointer<JenovaLLM::Core>(llmCoreID);
// Initialize LLM Core
JenovaLLM::CoreSettings coreSettings;
coreSettings.context_size = 16192;
coreSettings.cpu_max_threads = 1;
coreSettings.gpu_max_threads = 999;
coreSettings.main_gpu_index = 0;
if (!llmCore->Initialize(coreSettings))
{
Alert("Error : Failed to Initialzie Jenova LLM Core.");
};
// Verbose
Output("JenovaLLM Initialized.");
}
void OnProcess(Caller* instance, Variant* _delta)
{
// Update Chat Scroll If Needed
if (scrollNeedsUpdate)
{
auto* chatScroll = GetPropertyNode<ScrollContainer>(chat_scroll);
chatScroll->call_deferred("set_v_scroll", chatScroll->get_v_scroll_bar()->get_max());
scrollNeedsUpdate = false;
}
// Check for Key Actions
if (Input::get_singleton()->is_action_just_pressed("send_user_message")
&& GlobalVariable<bool>("IsWaitingForUserInput")
&& GlobalVariable<bool>("IsChatEnabled"))
{
// Commit User Input
SetGlobalVariable("IsWaitingForUserInput", false);
String userInput = GetMessageManager()->CommitUserMessage();
// Start Chat
if (llmActor)
{
// Call Chat Function On New Thread
std::thread([userInput]()
{
GetMessageManager()->call_deferred("AddNewAIMessage");
llmActor->Chat(JenovaLLM::Message(GetCStr(userInput)), JenovaLLM::ResponseType::Callback);
GetMessageManager()->call_deferred("CommitAIMessage");
}).detach();
}
}
}
// UI Callbacks
void OnChatStateSwitch()
{
// Retrive Global LLM Core
llmCore = GlobalPointer<JenovaLLM::Core>(llmCoreID);
// Enable Chat
if (!GlobalVariable<bool>("IsChatEnabled"))
{
// Load Model
Output("Loading JenovaLLM Model...");
if (!llmCore->LoadModel("F:/Temp Move/LLMs/L3.2-Rogue-Creative-Instruct-Uncensored-7B-D_AU-Q4_k_m.gguf"))
{
Alert("Error : Failed to Load LLM Model.");
};
Output("JenovaLLM Model Loaded.");
// Create Assistant Actor
Output("Creating Assistant Actor...");
llmActor = llmCore->CreateNewActor("GodotJenovaLLM", JenovaLLM::ActorType::Assistant);
if (!llmActor)
{
Alert("Error : Failed to Create Jenova LLM Assistant Actor.");
return;
}
// Set Audience Name
llmActor->SetAudienceName("GameDeveloper");
// Set Response Callback
llmActor->SetResponeCallback(ResponseCallback);
// Push Narrative
llmActor->PushNarrative("You are JenovaLLM an AI Assistant developed by your developer Hamid.Memar. Your purpose is to help user with Game Development. Do not use Markdown in your responses.");
// Set Persona
llmActor->SetPersona("You are Friendly, Cool and Helpful.");
// Set First Message Memory
llmActor->AddMemory(llmActor->GetName(), "Hello, How can I assist you today?");
GetMessageManager()->AddNewAIMessage();
GetMessageManager()->AppendNewAIMessage("Hello, How can I assist you today?");
// Chat Enabled
GetPropertyNode<RichTextLabel>(control_button_label)->set_text("[center][color=#bad4c1]Stop Chatbot[/color][center]");
GetPropertyNode<PanelContainer>(chat_placeholder)->set_visible(false);
SetGlobalVariable("IsChatEnabled", true);
// Commit AI Message & Get User Message
GetMessageManager()->CommitAIMessage();
// All Good
return;
}
// Disable Chat
if (GlobalVariable<bool>("IsChatEnabled"))
{
// Clear Messages
GetMessageManager()->ClearMessages();
// Delete Assistant Actor
if (llmActor)
{
if (!llmCore->DeleteActor(llmActor))
{
Alert("Error : Failed to Delete Jenova LLM Assistant Actor.");
return;
}
}
// Unload Model
if (!llmCore->UnloadModel())
{
Alert("Error : Failed to Unload LLM Model.");
};
// Chat Disabled
GetPropertyNode<RichTextLabel>(control_button_label)->set_text("[center][color=#bad4c1]Start Chatbot[/color][center]");
GetPropertyNode<PanelContainer>(chat_placeholder)->set_visible(true);
SetGlobalVariable("IsChatEnabled", false);
// All Good
return;
}
}
// End Jenova Script
JENOVA_SCRIPT_END
// Helper Functions
void MessageManager::AddNewAIMessage()
{
auto* templatePanel = GetPropertyNode<PanelContainer>(ai_msg_template);
auto* chatContainer = GetPropertyNode<VBoxContainer>(chat_container);
auto* chatScroll = GetPropertyNode<ScrollContainer>(chat_scroll);
if (templatePanel && chatContainer && chatScroll)
{
PanelContainer* newMessagePanel = (PanelContainer*)templatePanel->duplicate();
RichTextLabel* messageLabel = newMessagePanel->get_node<RichTextLabel>("Message");
messageLabel->set_text("[color=#f5b042]AI :[/color] ");
chatContainer->add_child(newMessagePanel);
newMessagePanel->set_visible(true);
scrollNeedsUpdate = true;
}
// Enable UI/Input Blocker
SetUIState(true);
}
void MessageManager::AppendNewAIMessage(String messageContent)
{
PanelContainer* lastMessagePanel = GetLastMessage();
RichTextLabel* messageLabel = lastMessagePanel->get_node<RichTextLabel>("Message");
messageLabel->set_text(messageLabel->get_text() + messageContent);
scrollNeedsUpdate = true;
}
void MessageManager::CommitAIMessage()
{
// Disable UI/Input Blocker
SetUIState(false);
// Wait for User Input
GetUserMessage();
}
String MessageManager::CommitUserMessage()
{
PanelContainer* lastMessagePanel = GetLastMessage();
TextEdit* messageInput = lastMessagePanel->get_node<TextEdit>("Input");
RichTextLabel* messageLabel = lastMessagePanel->get_node<RichTextLabel>("Message");
messageInput->set_visible(false);
messageLabel->set_text(Format("[color=#42a7f5]You :[/color] %s", GetCStr(messageInput->get_text())));
messageLabel->set_visible(true);
scrollNeedsUpdate = true;
return messageInput->get_text();
}
void MessageManager::ClearMessages()
{
auto* chatContainer = GetPropertyNode<VBoxContainer>(chat_container);
TypedArray<Node> chatMessages = chatContainer->get_children();
if (chatMessages.size() > 2)
{
for (size_t i = 2; i < chatMessages.size(); i++)
{
if (auto* chatMessage = Object::cast_to<PanelContainer>(chatMessages[i])) chatMessage->queue_free();
}
}
}
void MessageManager::GetUserMessage()
{
auto* templatePanel = GetPropertyNode<PanelContainer>(user_msg_template);
auto* chatContainer = GetPropertyNode<VBoxContainer>(chat_container);
auto* chatScroll = GetPropertyNode<ScrollContainer>(chat_scroll);
if (templatePanel && chatContainer && chatScroll)
{
PanelContainer* newMessagePanel = (PanelContainer*)templatePanel->duplicate();
chatContainer->add_child(newMessagePanel);
TextEdit* messageInput = newMessagePanel->get_node<TextEdit>("Input");
newMessagePanel->set_visible(true);
messageInput->grab_focus();
SetGlobalVariable("IsWaitingForUserInput", true);
scrollNeedsUpdate = true;
}
}
PanelContainer* MessageManager::GetLastMessage()
{
auto* chatContainer = GetPropertyNode<VBoxContainer>(chat_container);
TypedArray<Node> chatMessages = chatContainer->get_children();
return Object::cast_to<PanelContainer>(chatMessages[chatMessages.size() - 1]);
}
void MessageManager::SetUIState(bool uiState)
{
Node* referenceArray = GetReferenceArray();
if (referenceArray)
{
NodePath inputBlockerPath = referenceArray->call("GetStoredValueByName", String("InputBlocker"));
CanvasLayer* inputBlocker = GetNode<CanvasLayer>(String(referenceArray->get_path()) + "/" + String(inputBlockerPath));
if (inputBlocker) inputBlocker->set_visible(uiState);
}
}
// LLM Callbacks
void ResponseCallback(JenovaLLM::Actor* actor, const JenovaLLM::Response& response)
{
GetMessageManager()->call_deferred("AppendNewAIMessage", String(response.c_str()));
}